package cn.edu.zust.se.service.file.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.edu.zust.se.api.entity.File;
import cn.edu.zust.se.api.entity.Folder;
import cn.edu.zust.se.api.entity.bo.FileBo;
import cn.edu.zust.se.api.entity.bo.FileChunkDTO;
import cn.edu.zust.se.api.entity.vo.FileChunkResultDTO;
import cn.edu.zust.se.api.entity.vo.FileVo;
import cn.edu.zust.se.service.file.commom.UpLoadConstant;
import cn.edu.zust.se.service.file.fastdfs.FastDFSClientWrapper;
import cn.edu.zust.se.service.file.fastdfs.FastDFSService;
import cn.edu.zust.se.service.file.fastdfs.FastdfsConfig;
import cn.edu.zust.se.service.file.mapper.FileMapper;
import cn.edu.zust.se.service.file.mapper.FolderMapper;
import cn.edu.zust.se.service.file.service.FileService;
import cn.edu.zust.se.service.file.utils.SimpleFileUtil;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.*;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
@Slf4j
public class FileServiceImpl extends ServiceImpl<FileMapper, File> implements FileService {
    private final FileMapper fileMapper;
    private final FastDFSService fastDFSService;
    private final FolderMapper folderMapper;
    private final StringRedisTemplate stringRedisTemplate;
    private final RedisTemplate<String,Object> redisTemplate;
    private final FastFileStorageClient storageClient;
    @Resource
    private FastDFSClientWrapper fastDFSClientWrapper ;
    @Resource
    private FastdfsConfig fastdfsConfig ;
    @Value("${tmp.path}")
    private String uploadFolder;
    @Value("${fdfs.outurl}")
    private String uploadPath;


    @Override
    public List<File> getFilesByFolderId(Long folderId,Integer type) {
        LambdaQueryWrapper<File> lqw = new LambdaQueryWrapper<>();
        if (type == 2)
        {
            lqw.eq(File::getDelFlag, 2);
        }else {
            lqw.eq(File::getDelFlag, 0);
        }
        lqw.eq(File::getUserId, StpUtil.getLoginIdAsLong());
        lqw.eq(File::getFolderId, folderId);
        return fileMapper.selectList(lqw);
    }

    @Override
    public void test() {
        File file = new File();
        file.setPath("aa");
        file.setType(1);
        file.setOriginalName("AA");
        file.setSize("1KB");
        file.setFolderId(22l);
        fileMapper.insert(file);
    }

    @Override
    public void insertFile(File uploadFile) {
        boolean success = fileMapper.insert(uploadFile) > 0;
        if (!success) {
            throw new RuntimeException("文件上传失败");
        }
    }

    @Override
    public FileVo getFileByMd5(String md5) {
        LambdaQueryWrapper<File> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(File::getMd5, md5);
        wrapper.eq(File::getDelFlag, 0);
        File file = fileMapper.selectOne(wrapper);
        if (file != null) {
            return BeanUtil.copyProperties(file, FileVo.class);
        }
        return null;
    }

    @Override
    public Boolean instantTransmit(Long fileId, Long folderId) {
        File file = fileMapper.selectById(fileId);
        if (file != null) {
            file.setFolderId(folderId);
            file.setId(null);
            file.setCreateBy(null);
            file.setCreateTime(null);
            file.setUpdateBy(null);
            file.setUpdateTime(null);
            return fileMapper.insert(file) > 0;
        }
        return false;
    }

    @Override
    public ResponseEntity<byte[]> download(Long fileId) {
        File file = fileMapper.selectUndeletedFileById(fileId);
        if (file != null) {
            try {
                String url = file.getPath();
                // 找到第一个斜杠的位置
                int firstSlashIndex = url.indexOf("/");
                // 进行一次分割，从第一个斜杠的位置开始
                String result = url.substring(firstSlashIndex+1);
                System.out.println(result.toString());
                byte[] bytes = fastDFSService.downloadFile(result.toString());
                String encodedFileName = URLEncoder.encode(file.getOriginalName(), StandardCharsets.UTF_8.toString());
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
                headers.setContentDispositionFormData("attachment", encodedFileName);
                return ResponseEntity.ok()
                        .headers(headers)
                        .body(bytes);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    @Override
    public byte[] downloadFile(Long fileId) {
        File file = baseMapper.selectUndeletedFileById(fileId);
        if (file != null) {
            try {
                String url = file.getPath();
                // 找到第一个斜杠的位置
                int firstSlashIndex = url.indexOf("/");
                // 进行一次分割，从第一个斜杠的位置开始
                String result = url.substring(firstSlashIndex+1);
                System.out.println(result.toString());
                return fastDFSService.downloadFile(result);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean clearAllByUserId(Long userId) {
        LambdaQueryWrapper<File> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(File::getUserId, userId);
        wrapper.eq(File::getDelFlag, 2);
        List<File> files = fileMapper.selectList(wrapper);
        List<Long> ids = files.stream().map(File::getId).collect(Collectors.toList());
        if (!ids.isEmpty()) {
            boolean success = baseMapper.deleteBatchIds(ids) > 0;
            if (!success) {
                throw new RuntimeException("清空文件失败");
            }
        }
        boolean success1 = folderMapper.delete(Wrappers.<Folder>lambdaQuery().eq(Folder::getUserId, userId).eq(Folder::getDelFlag, 2)) > 0;
        if (!success1) {
            throw new RuntimeException("清空文件夹失败");
        }
        files.stream().forEach(file -> {
            try {
                fastDFSService.deleteFile(file.getPath());
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        return true;
    }

    @Override
    public Boolean clearFileById(Long id) {
        File file = fileMapper.selectDeletedFileById(id);
        if (file == null) {
            return false;
        }
        boolean success = fileMapper.deleteById(id) > 0;
        if (!success) {
            throw new RuntimeException("删除文件失败");
        }
        try {
            String url = file.getPath();
            // 找到第一个斜杠的位置
            int firstSlashIndex = url.indexOf("/");
            // 进行一次分割，从第一个斜杠的位置开始
            String result = url.substring(firstSlashIndex+1);
            fastDFSService.deleteFile(result);
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        return true;

    }

    @Override
    public Boolean recycle(Long fileId) {
        File file = fileMapper.selectDeletedFileById(fileId);
        if (file == null) {
            return false;
        }
        file.setDelFlag(0);
        return fileMapper.updateById(file) > 0;
    }

    @Override
    public boolean move(FileBo fileBo) {
        Folder folder = folderMapper.selectUndeletedFolderById(fileBo.getFolderId());
        if (folder == null) {
            return false;
        }
        File file = fileMapper.selectById(fileBo.getId());
        if (file == null) {
            return false;
        }
        file.setFolderId(fileBo.getFolderId());
        return fileMapper.updateById(file) > 0;
    }

    @Override
    public FileVo check(String identifier, Long folderId) {
        LambdaQueryWrapper<File> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(File::getMd5, identifier);
        wrapper.eq(File::getDelFlag, 0);
        File file = fileMapper.selectOne(wrapper);
        FileVo fileVo = new FileVo();
        if (file != null) {
            BeanUtil.copyProperties(file, fileVo);
            fileVo.setExist(true);
            file.setId(null);
            file.setFolderId(folderId);
            file.setUserId(StpUtil.getLoginIdAsLong());
            baseMapper.insert(file);
            return fileVo;
        }
        Set<String> members = stringRedisTemplate.opsForSet().members(UpLoadConstant.uploaded + identifier);
        Set<Integer> uploaded = members.stream().map(Integer::parseInt).collect(Collectors.toSet());
        if (uploaded.isEmpty() ) {
            fileVo.setExist(false);
            return fileVo;
        }
        fileVo.setExist(false);
        fileVo.setUploadedChunks(uploaded);
        return fileVo;
    }

    @Override
    public Boolean copyFile(Long fileId, Long folderId) {
        File file = fileMapper.selectById(fileId);
        File copyFile = new File();
        BeanUtil.copyProperties(file, copyFile);
        copyFile.setFolderId(folderId);
        copyFile.setId(null);
        copyFile.setCreateBy(null);
        copyFile.setCreateTime(null);
        copyFile.setUpdateBy(null);
        copyFile.setUpdateTime(null);
        return fileMapper.insert(copyFile) > 0;
    }

    /**
     * 检查文件是否存在，如果存在则跳过该文件的上传，如果不存在，返回需要上传的分片集合
     *
     * @param chunkDTO
     * @return
     */
    @Override
    public FileChunkResultDTO checkChunkExist(FileChunkDTO chunkDTO) {
        //1.检查文件是否已上传过
        //1.1)检查在磁盘中是否存在
        LambdaQueryWrapper<File> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(File::getMd5, chunkDTO.getIdentifier());
        wrapper.eq(File::getDelFlag, 0);
        File file = fileMapper.selectOne(wrapper);
        //1.2)检查Redis中是否存在,并且所有分片已经上传完成。
        Set<Integer> uploaded = (Set<Integer>) redisTemplate.opsForHash().get(chunkDTO.getIdentifier(), "uploaded");
        if (file != null) {
            //秒传
            return new FileChunkResultDTO(true);
        }
        // 断点续传，返回已上传的分片
        return new FileChunkResultDTO(false, uploaded);
    }


    /**
     * 上传分片
     *
     * @param chunkDTO
     */
    @Override
    public void uploadChunk(FileChunkDTO chunkDTO) {
        //分块的目录
        String chunkFileFolderPath = getChunkFileFolderPath(chunkDTO.getIdentifier());
        log.info("分块的目录 -> {}", chunkFileFolderPath);
        java.io.File chunkFileFolder = new java.io.File(chunkFileFolderPath);
        if (!chunkFileFolder.exists()) {
            boolean mkdirs = chunkFileFolder.mkdirs();
            log.info("创建分片文件夹:{}", mkdirs);
        }
        //写入分片
        try (
                InputStream inputStream = chunkDTO.getFile().getInputStream();
                FileOutputStream outputStream = new FileOutputStream(new java.io.File(chunkFileFolderPath + chunkDTO.getChunkNumber()))
        ) {
            IOUtils.copy(inputStream, outputStream);
            log.info("文件标识:{},chunkNumber:{}", chunkDTO.getIdentifier(), chunkDTO.getChunkNumber());
            //将该分片写入redis
            long size = saveToRedis(chunkDTO);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    @Override
    public boolean mergeChunk(String identifier, String fileName, Integer totalChunks, Long folderId) throws FileNotFoundException {
        return mergeChunks(identifier, fileName, totalChunks,folderId);
    }

    @Override
    public Long getRootFolderId(Long userId) {
        LambdaQueryWrapper<Folder> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Folder::getUserId, userId);
        wrapper.eq(Folder::getDelFlag, 0);
        wrapper.eq(Folder::getParentId, 0L);
        Folder folder = folderMapper.selectOne(wrapper);
        if (folder != null) {
            return folder.getId();
        }
        return null;
    }

    /**
     * 合并分片
     *
     * @param identifier
     * @param filename
     * @param folderId
     */
    private boolean mergeChunks(String identifier, String filename, Integer totalChunks, Long folderId) throws FileNotFoundException {
        String chunkFileFolderPath = getChunkFileFolderPath(identifier);
        String filePath = getFilePath(identifier, filename);
        // 检查分片是否都存在
        if (checkChunks(chunkFileFolderPath, totalChunks)) {
            java.io.File chunkFileFolder = new java.io.File(chunkFileFolderPath);
            java.io.File mergeFile = new java.io.File(filePath);
            java.io.File[] chunks = chunkFileFolder.listFiles();
            //排序
            List fileList = Arrays.asList(chunks);
            Collections.sort(fileList, (Comparator<java.io.File>) (o1, o2) -> {
                return Integer.parseInt(o1.getName()) - (Integer.parseInt(o2.getName()));
            });
            try {
                RandomAccessFile randomAccessFileWriter = new RandomAccessFile(mergeFile, "rw");
                byte[] bytes = new byte[1024];
                for (java.io.File chunk : chunks) {
                    RandomAccessFile randomAccessFileReader = new RandomAccessFile(chunk, "r");
                    int len;
                    while ((len = randomAccessFileReader.read(bytes)) != -1) {
                        randomAccessFileWriter.write(bytes, 0, len);
                    }
                    randomAccessFileReader.close();
                }
                randomAccessFileWriter.close();
                byte[] content = SimpleFileUtil.getContent(filePath);
                String url =  fastDFSClientWrapper.uploadFile(content, mergeFile.length(), SimpleFileUtil.getExtName(filename));
                String resultUrl = fastdfsConfig.getOuturl() + url;
                File file = new File();
                file.setFolderId(folderId);
                file.setPath(resultUrl);
                file.setOriginalName(filename);
                file.setUserId(StpUtil.getLoginIdAsLong());
                file.setMd5(identifier);
                file.setSize(String.valueOf(mergeFile.length()));
                file.setType(SimpleFileUtil.getTypeByFile(mergeFile));
                baseMapper.insert(file);
            } catch (Exception e) {
                return false;
            }
            log.info("合并分片成功,文件路径:{}", filePath);
            return true;
        }
        return false;
    }

    /**
     * 检查分片是否都存在
     * @param chunkFileFolderPath
     * @param totalChunks
     * @return
     */
    private boolean checkChunks(String chunkFileFolderPath, Integer totalChunks) {
        try {
            for (int i = 1; i <= totalChunks + 1; i++) {
                java.io.File file = new java.io.File(chunkFileFolderPath + java.io.File.separator + i);
                if (file.exists()) {
                    continue;
                } else {
                    return false;
                }
            }
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    /**
     * 分片写入Redis
     *
     * @param chunkDTO
     */
    private synchronized long saveToRedis(FileChunkDTO chunkDTO) {
        Set<Integer> uploaded = (Set<Integer>) redisTemplate.opsForHash().get(chunkDTO.getIdentifier(), "uploaded");
        if (uploaded == null) {
            uploaded = new HashSet<>(Arrays.asList(chunkDTO.getChunkNumber()));
            HashMap<String, Object> objectObjectHashMap = new HashMap<>();
            objectObjectHashMap.put("uploaded", uploaded);
            objectObjectHashMap.put("totalChunks", chunkDTO.getTotalChunks());
            objectObjectHashMap.put("totalSize", chunkDTO.getTotalSize());
//            objectObjectHashMap.put("path", getFileRelativelyPath(chunkDTO.getIdentifier(), chunkDTO.getFilename()));
            objectObjectHashMap.put("path", chunkDTO.getFilename());
            redisTemplate.opsForHash().putAll(chunkDTO.getIdentifier(), objectObjectHashMap);
        } else {
            uploaded.add(chunkDTO.getChunkNumber());
            redisTemplate.opsForHash().put(chunkDTO.getIdentifier(), "uploaded", uploaded);
        }
        return uploaded.size();
    }

    /**
     * 得到文件的绝对路径
     *
     * @param identifier
     * @param filename
     * @return
     */
    private String getFilePath(String identifier, String filename) {
        String ext = filename.substring(filename.lastIndexOf("."));
//        return getFileFolderPath(identifier) + identifier + ext;
        return uploadFolder +"\\"+ filename;
    }

    /**
     * 得到文件的相对路径
     *
     * @param identifier
     * @param filename
     * @return
     */
    private String getFileRelativelyPath(String identifier, String filename) {
        String ext = filename.substring(filename.lastIndexOf("."));
        return "/" + identifier.substring(0, 1) + "/" +
                identifier.substring(1, 2) + "/" +
                identifier + "/" + identifier
                + ext;
    }


    /**
     * 得到分块文件所属的目录
     *
     * @param identifier
     * @return
     */
    private String getChunkFileFolderPath(String identifier) {
        return getFileFolderPath(identifier) + "chunks" + java.io.File.separator;
    }

    /**
     * 得到文件所属的目录
     *
     * @param identifier
     * @return
     */
    private String getFileFolderPath(String identifier) {
        return uploadFolder + identifier.substring(0, 1) + java.io.File.separator +
                identifier.substring(1, 2) + java.io.File.separator +
                identifier + java.io.File.separator;
//        return uploadFolder;
    }
    @Override
    public List<File> queryList(FileBo fileBo, Integer type) {
        LambdaQueryWrapper<File> lqw = new LambdaQueryWrapper<>();
        //当前登录用户
        if (type == 2) {
            lqw.eq(File::getDelFlag, 2);
        } else {
            lqw.eq(File::getDelFlag, 0);
        }
        lqw.eq(File::getUserId, StpUtil.getLoginIdAsLong());
        lqw.like(fileBo.getOriginalName() != null, File::getOriginalName, fileBo.getOriginalName());
        lqw.eq(fileBo.getType() != null, File::getType, fileBo.getType());
        return fileMapper.selectList(lqw);
    }


}
